package com.sghd.charge.module.payment;

import com.sghd.charge.module.Config;
import com.sghd.charge.module.model.*;
import com.sghd.charge.module.order.entity.Order;
import com.sghd.charge.module.order.entity.Server;
import com.sghd.charge.module.order.repository.OrderRepository;
import com.sghd.charge.module.order.repository.ServerRepository;
import com.sghd.common.scheduler.impl.FixTimeDelayQueue;
import com.sghd.common.utils.ManagedException;
import com.sghd.common.utils.concurrent.DelayedElement;
import com.sghd.common.utils.json.JsonUtils;
import com.sghd.common.utils.model.Result;
import com.sghd.common.utils.time.DateUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author frank
 */

@Service
public class ChargeService {

    public final static String TRANSACTION_NUMBER = "TRANSACTION_NUMBER";
    public final static String TOTAL_FEE = "totalFee";
    public final static String CHANNEL_TYPE = "channelType";
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ServerRepository serverRepository;
    @Autowired
    @Qualifier("httpTemplate")
    private RestTemplate restTemplate;
    @Autowired
    private Config config;
    /** 订单锁*/
    private ConcurrentMap<Long, Lock> orderLocks = new ConcurrentHashMap();
    private FixTimeDelayQueue<DelayedElement<RetryCharge>> retryQueue;

    @PostConstruct
    protected void init() {
        retryQueue = new FixTimeDelayQueue<>(config.getDelayTimes());
        List<Order> orders = orderRepository.findWait();
        for (Order order : orders) {
            RetryCharge retryCharge = RetryCharge.valueOf(order.getServerId(), order.getId());
            retryQueue.put(new DelayedElement<>(retryCharge, order.getRetryTime()));
            if (logger.isDebugEnabled()) {
                logger.debug("重新载入补单信息[{}]计划执行时间[{}]", retryCharge, DateUtils.date2String(order.getRetryTime(), DateUtils.PATTERN_DATE_TIME));
            }
        }
        Thread retryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        DelayedElement<RetryCharge> e = retryQueue.take();
                        RetryCharge retryCharge = e.getContent();
                        if (logger.isDebugEnabled()) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("补单信息[{}]到达执行时间[{}]", retryCharge, DateUtils.date2String(e.getEnd(), DateUtils.PATTERN_DATE_TIME));
                            }
                        }
                        Optional<Order> orderOptional = orderRepository.findById(retryCharge.getOrderId());
                        if (!orderOptional.isPresent()) {
                            // 充值订单不存
                            continue;
                        }
                        Order order = orderOptional.get();
                        if (order.getState() != ChargeState.WAIT) {
                            //订单状态不正确
                            continue;
                        }
                        doRetryCharge(order);
                    } catch (InterruptedException e) {
                        logger.error("自动补单线程被打断", e);
                    } catch (Exception e) {
                        logger.error("自动补单线程出现未知异常", e);
                    }
                }
            }
        }, "充值重试");
        retryThread.setDaemon(true);
        retryThread.start();
    }

    public void charge(long orderId, Map<String, Object> attachment) {
        Order order = null;
        Lock lock = lockByOrderId(orderId);
        try {
            Optional<Order> optional = orderRepository.findById(orderId);
            if (!optional.isPresent()) {
                throw new ManagedException(ChargeCode.ORDER_NOT_FOUND.getValue(), ChargeCode.ORDER_NOT_FOUND.getDescription());
            }
            order = optional.get();
            Optional<Server> serverOptional = serverRepository.findById(order.getServerId());
            if (!serverOptional.isPresent()) {
                throw new ManagedException(ChargeCode.SERVER_NOT_FOUND.getValue(), ChargeCode.SERVER_NOT_FOUND.getDescription() + ":" + order.getServerId());
            }
            //判断订单是否已经充值完成
            if (order.getState() == ChargeState.COMPLETE) {
                logger.info("该笔订单已经完成！");
                return;
            }
            String channelType = MapUtils.getString(attachment, CHANNEL_TYPE);
            String transactionNumber = MapUtils.getString(attachment, TRANSACTION_NUMBER);
            //验证交易流水号的唯一性
            List<Order> completes = orderRepository.findByTransactionId(ChannelType.valueOf(channelType), transactionNumber);
            if (completes != null && !completes.isEmpty()) {
                logger.info("该笔订单已经完成！");
                return;
            }
            int currentMoney = MapUtils.getIntValue(attachment, TOTAL_FEE);
            if (currentMoney != order.getMoney()) {
                logger.error("订单[{}]金额[{}]与实际支付金额[{}]不匹配！", orderId, order.getMoney(), currentMoney);
                return;
            }
            order.setTransactionNumber(transactionNumber);
            Server server = serverOptional.get();
            Result<String> result = null;
            try {
                ChargeDto request = ChargeDto.valueOf(order.getPlayerId(), order.getId(), order.getGoodsId(), order.getIp(), order.getChannel());
                HttpEntity<String> httpEntity = new HttpEntity<>(JsonUtils.object2String(request));
                result = restTemplate.postForObject(server.getUrl(), httpEntity, Result.class);
            } catch (Exception e) {
                throw new ManagedException(ChargeCode.SOCKET_ERROR.getValue(), ChargeCode.SOCKET_ERROR.getDescription());
            }
            if (result.getCode() == 0) {
                order.success(ChargeCode.DONE, "");
            } else {
                throw new ManagedException(ChargeCode.SERVER_RETURN_FAIL.getValue(), ChargeCode.SERVER_RETURN_FAIL.getDescription() + ":" + result.getContent());
            }
            //写入数据库
            orderRepository.save(order);
        } catch (ManagedException e) {
            logger.error("发送充值奖励失败ERROR_CODE:{},MESSAGE:{}", e.getCode(), e.getMessage());
            ChargeCode chargeCode = ChargeCode.valueOf(e.getCode());
            if (chargeCode.isRetry()) {
                logger.error("补单信息[{}]下一次执行时间[{}]", order.getId(), DateUtils.date2String(getNextRetryTime(0), DateUtils.PATTERN_DATE_TIME));
                // 记录并准备进行充值重试
                order.prepareRetry(chargeCode, chargeCode.getDescription(), getNextRetryTime(0));
                retryQueue.put(DelayedElement.valueOf(RetryCharge.valueOf(order.getServerId(), order.getId()), order.getRetryTime()));
            } else {
                order.failure(chargeCode, e.getMessage());
            }
        } catch (Exception e) {
            order.failure(ChargeCode.UNKNOWN_ERROR, e.getMessage());
            logger.error("发送充值奖励发生未知错误！{}", e.getMessage());
        } finally {
            unLockByOrderId(orderId, lock);
        }
    }


    /**
     * 自动补单
     * @param order
     */
    protected void doRetryCharge(Order order) {
        Lock lock = lockByOrderId(order.getId());
        try {
            Optional<Server> serverOptional = serverRepository.findById(order.getServerId());
            if (!serverOptional.isPresent()) {
                throw new ManagedException(ChargeCode.SERVER_NOT_FOUND.getValue(), ChargeCode.SERVER_NOT_FOUND.getDescription() + ":" + order.getServerId());
            }
            //判断订单是否已经充值完成
            if (order.getState() == ChargeState.COMPLETE) {
                logger.info("该笔订单已经完成！");
                return;
            }
            Server server = serverOptional.get();
            Result<String> result = null;
            try {
                ChargeDto request = ChargeDto.valueOf(order.getPlayerId(), order.getId(), order.getGoodsId(), order.getIp(), order.getChannel());
                HttpEntity<String> httpEntity = new HttpEntity<>(JsonUtils.object2String(request));
                result = restTemplate.postForObject(server.getUrl(), httpEntity, Result.class);
            } catch (Exception e) {
                throw new ManagedException(ChargeCode.SOCKET_ERROR.getValue(), ChargeCode.SOCKET_ERROR.getDescription());
            }
            if (result.getCode() == 0) {
                order.success(ChargeCode.DONE, "");
            } else {
                throw new ManagedException(ChargeCode.SERVER_RETURN_FAIL.getValue(), ChargeCode.SERVER_RETURN_FAIL.getDescription() + ":" + result.getContent());
            }
        } catch (ManagedException e) {
            logger.error("自动补单失败ERROR_CODE:{},MESSAGE:{}", e.getCode(), e.getMessage());
            ChargeCode chargeCode = ChargeCode.valueOf(e.getCode());
            // 补单失败
            order.retryFailed(chargeCode, getNextRetryTime(order.getRetry() + 1));
            if (chargeCode.isRetry() && order.getRetry() < config.getRetryMax()) {
                // 准备进行再次充值重试
                retryQueue.add(DelayedElement.valueOf(RetryCharge.valueOf(order.getServerId(), order.getId()), order.getRetryTime()));
            } else {
                order.failure(chargeCode, e.getMessage());
            }
        } catch (Exception e) {
            order.failure(ChargeCode.UNKNOWN_ERROR, e.getMessage());
            logger.error("自动被单发生未知错误！{}", e.getMessage());
        } finally {
            unLockByOrderId(order.getId(), lock);
        }
        //写入数据库
        orderRepository.save(order);
    }

    /** 获取下次重试的时间 */
    private Date getNextRetryTime(int times) {
        int sec = config.getRetryInterval() * (times / config.getRetryTimes() + 1);
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, sec);
        return calendar.getTime();
    }

    private Lock lockByOrderId(long orderId) {
        Lock lock = orderLocks.get(orderId);
        if (lock == null) {
            lock = new ReentrantLock();
            Lock prev = orderLocks.putIfAbsent(orderId, lock);
            lock = prev == null ? lock : prev;
        }
        lock.lock();
        return lock;
    }

    /** 释放标识锁 */
    private void unLockByOrderId(long id, Lock lock) {
        lock.unlock();
        orderLocks.remove(id);
    }
}